// 19.8 固有的不可移植的特性
/**
 * 为了支持低层编程，C++定义了一些固有的不可移植（nonportable）的特性。
 * 所谓不可移植的特性是指因机器而异的特性，当我们将含有不可移植特性的程序从一台机器转移到另一台机器上时，通常需要重新编写该程序。
 * 算术类型的大小在不同机器上不一样（参见2.1.1节，第30页），这是我们使用过的不可移植特性的一个典型示例。
 * 本节将介绍C++从C语言继承而来的另外两种不可移植的特性：位域和volatile限定符。此外，我们还将介绍链接指示，它是C++新增的一种不可移植的特性。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <typeinfo>

int main()
{
    // 19.8.1 位域
    /*类可以将其（非静态）数据成员定义成位域（bit-field），在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时，通常会用到位域。
      位域在内存中的布局是与机器相关的。
      位域的类型必须是整型或枚举类型（参见19.3节，第736页）。因为带符号位域的行为是由具体实现确定的，所以在通常情况下我们使用无符号类型保存一个位域。
        位域的声明形式是在成员名字之后紧跟一个冒号以及一个常量表达式，该表达式用于指定成员所占的二进制位数：[插图]
typedef unsigned int Bit;
class File {
    Bit mode:2;       // mode占2位
    Bit modified:1;   // modified占1位
    Bit prot_owner:3; // prot_owner占3位
    Bit prot_group:3; // prot_group占3位
    Bit prot_world:3; // prot_world占3位
    // File的操作和数据成员
public:
    // 文件类型以八进制的形式表示，参见2.1.3节
    enum modes { READ=01, WRITE=02, EXECUTE=03 };
    File &open(modes);
    void close();
    void write();
    bool isRead() const;
    void setWrite();
};
      如果可能的话，在类的内部连续定义的位域压缩在同一整数的相邻位，从而提供存储压缩。例如在之前的声明中，五个位域可能会存储在同一个unsigned int中。
        这些二进制位是否能压缩到一个整数中以及如何压缩是与机器相关的。
      取地址运算符（&）不能作用于位域，因此任何指针都无法指向类的位域。
      通常情况下最好将位域设为无符号类型，存储在带符号类型中的位域的行为将因具体实现而定。
    */

    // 使用位域
    /*访问位域的方式与访问类的其他数据成员的方式非常相似：[插图][插图]
void File::write()
{
    modified = 1;
    // ...
}
void File::close()
{
    if(modified)
        // ...保存内容
}
      通常使用内置的位运算符（参见4.8节，第136页）操作超过1位的位域：[插图]
File &File::open(File::modes m)
{
    mode |= READ;    // 按默认方式设置READ
    // 其他处理
    if(m & WRITE)    // 如果打开了READ和WRITE
        // 按照读/写方式打开文件
    return *this;
}
      如果一个类定义了位域成员，则它通常也会定义一组内联的成员函数以检验或设置位域的值：[插图]
inline bool File::isRead() const { reutrn mode & READ; }
inline void File::setWrite() { mode |= WRITE; }
    */

    // 19.8.2 volatile限定符
    /*volatile的确切含义与机器有关，只能通过阅读编译器文档来理解。要想让使用了volatile的程序在移植到新机器或新编译器后仍然有效，通常需要对该程序进行某些改变。
      直接处理硬件的程序常常包含这样的数据元素，它们的值由程序直接控制之外的过程控制。例如，程序可能包含一个由系统时钟定时更新的变量。当对象的值可能在程序的控制或检测之外被改变时，
        应该将该对象声明为volatile。关键字volatile告诉编译器不应对这样的对象进行优化。
      volatile限定符的用法和const很相似，它起到对类型额外修饰的作用：[插图]
volatile int display_register;  // 该int值可能发生改变
volatile Task *curr_task;       // curr_task指向一个volatile对象
volatile int iax[max_size];     // iax的每个元素都是volatile
volatile Screen bitmapBuf;      // bitmapBuf的每个成员都是volatile
      只有volatile的成员函数才能被volatile的对象调用。
      2.4.2节（第56页）描述了const限定符和指针的相互作用，在volatile限定符和指针之间也存在类似的关系。我们可以声明volatile指针、指向volatile对象的指针以及指向volatile对象的volatile指针：[插图][插图]
volatile int v;    // v是一个volatile int
int *volatile vip; // vip是一个volatile指针，它指向int
volatile int *ivp; // ivp是一个指针，它指向一个volatile int
// vivp 是一个volatile指针，它指向一个volatile int
volatile int *volatile vivp;
int *ip = &v;      // 错误：必须使用指向volatile的指针
ivp = &v;          // 正确：ivp是一个指向volatile的指针
vivp = &v;         // 正确：vivp是一个指向volatile的volatile指针
    */

    // 合成的拷贝对volatile对象无效
    /*const和volatile的一个重要区别是我们不能使用合成的拷贝/移动构造函数及赋值运算符初始化volatile对象或从volatile对象赋值。合成的成员接受的形参类型是（非volatile）常量引用，
        显然我们不能把一个非volatile引用绑定到一个volatile对象上。
      如果一个类希望拷贝、移动或赋值它的volatile对象，则该类必须自定义拷贝或移动操作。例如，我们可以将形参类型指定为const volatile引用，这样我们就能利用任意类型的Foo进行拷贝或赋值操作了：[插图]
class Foo {
public:
    Foo(const volatile Foo&); // 从一个volatile对象进行拷贝
    // 将一个volatile对象赋值给一个非volatile对象
    Foo& operator=(volatile const Foo&);
    // 将一个volatile对象赋值给一个volatile对象
    Foo& operator=(volatile const Foo&) volatile;
    // Foo类的剩余部分
};
      尽管我们可以为volatile对象定义拷贝和赋值操作，但是一个更深层次的问题是拷贝volatile对象是否有意义呢？不同程序使用volatile的目的各不相同，对上述问题的回答与具体的使用目的密切相关。
    */

    // 19.8.3 链接指示：extern "C"
    /*C++程序有时需要调用其他语言编写的函数，最常见的是调用C语言编写的函数。像所有其他名字一样，其他语言中的函数名字也必须在C++中进行声明，并且该声明必须指定返回类型和形参列表。
        对于其他语言编写的函数来说，编译器检查其调用的方式与处理普通C++函数的方式相同，但是生成的代码有所区别。C++使用链接指示（linkage directive）指出任意非C++函数所用的语言。
      要想把C++代码和其他语言（包括C语言）编写的代码放在一起使用，要求我们必须有权访问该语言的编译器，并且这个编译器与当前的C++编译器是兼容的。
    */
    
    // 声明一个非C++的函数
    /*链接指示可以有两种形式：单个的或复合的。链接指示不能出现在类定义或函数定义的内部。同样的链接指示必须在函数的每个声明中都出现。
      举个例子，接下来的声明显示了cstring头文件的某些C函数是如何声明的：[插图]
// 可能出现在C++头文件<cstring>中的链接指示
// 语句链接指示
extern "C" size_t strlen(const char *);
// 复合语句链接指示
extern "C" {
    int strcmp(const char*, const char*);
    char* strcat(char*, const char*);
}
      链接指示的第一种形式包含一个关键字extern，后面是一个字符串字面值常量以及一个“普通的”函数声明。
      其中的字符串字面值常量指出了编写函数所用的语言。编译器应该支持对C语言的链接指示。此外，编译器也可能会支持其他语言的链接指示，如extern "Ada"、extern"FORTRAN"等。
    */

    // 链接指示与头文件
    /*我们可以令链接指示后面跟上花括号括起来的若干函数的声明，从而一次性建立多个链接。花括号的作用是将适用于该链接指示的多个声明聚合在一起，否则花括号就会被忽略，
        花括号中声明的函数名字就是可见的，就好像在花括号之外声明的一样。
      多重声明的形式可以应用于整个头文件。例如，C++的cstring头文件可能形如：[插图]
// 复合语句链接指示
extern "C" {
    #include <string.h> // 操作C风格字符串的C函数
}
      当一个#include指示被放置在复合链接指示的花括号中时，头文件中的所有普通函数声明都被认为是由链接指示的语言编写的。
        链接指示可以嵌套，因此如果头文件包含带自带链接指示的函数，则该函数的链接不受影响。
      C++从C语言继承的标准库函数可以定义成C函数，但并非必须：决定使用C还是C++实现C标准库，是每个C++实现的事情。
    */

    // 指向extern "C"函数的指针
    /*编写函数所用的语言是函数类型的一部分。因此，对于使用链接指示定义的函数来说，它的每个声明都必须使用相同的链接指示。而且，指向其他语言编写的函数的指针必须与函数本身使用相同的链接指示：[插图]
// pf指向一个C函数，该函数接受一个int返回void
extern "C" void (*pf)(int);
      当我们使用pf调用函数时，编译器认定当前调用的是一个C函数。
      指向C函数的指针与指向C++函数的指针是不一样的类型。一个指向C函数的指针不能用在执行初始化或赋值操作后指向C++函数，反之亦然。就像其他类型不匹配的问题一样，
        如果我们试图在两个链接指示不同的指针之间进行赋值操作，则程序将发生错误：[插图]
void (*pf1)(int);            // 指向一个C++函数
extern "C" void (*pf2)(int); // 指向一个C函数
pf1 = pf2;                   // 错误：pf1和pf2的类型不同
      有的C++编译器会接受之前的这种赋值操作并将其作为对语言的扩展，尽管从严格意义上来看它是非法的。
    */

    // 链接指示对整个声明都有效
    /*当我们使用链接指示时，它不仅对函数有效，而且对作为返回类型或形参类型的函数指针也有效：[插图]
extern "C" void f1(void(*)(int)); // f1是一个C函数，它的形参是一个指向C函数的指针
      因为链接指示同时作用于声明语句中的所有函数，所以如果我们希望给C++函数传入一个指向C函数的指针，则必须使用类型别名（参见2.5.1节，第60页）：[插图]
extern "C" typedef void FC(int); // FC是一个指向C函数的指针
void f2(FC*);                    // f2是一个C++函数，它的形参是一个指向C函数的指针
    */

    // 导出C++函数到其他语言
    /*通过使用链接指示对函数进行定义，我们可以令一个C++函数在其他语言编写的程序中可用：[插图]
extern "C" double calc(double dparm) { // ... } // calc函数可以被C程序调用
      编译器将为该函数生成适合于指定语言的代码。
      值得注意的是，可被多种语言共享的函数的返回类型或形参类型受到很多限制。例如，我们不太可能把一个C++类的对象传给C程序，因为C程序根本无法理解构造函数、析构函数以及其他类特有的操作。
    */

    // 对链接到C的预处理器的支持
    /*有时需要在C和C++中编译同一个源文件，为了实现这一目的，在编译C++版本的程序时预处理器定义_ _cplusplus（两个下画线）。利用这个变量，我们可以在编译C++程序的时候有条件地包含进来一些代码：[插图]
#ifdef __cplusplus
// 正确：我们正在编译C++程序
extern "C"
#endif
int strcmp(const char*, const char*);
    */

    // 重载函数与链接指示
    /*链接指示与重载函数的相互作用依赖于目标语言。如果目标语言支持重载函数，则为该语言实现链接指示的编译器很可能也支持重载这些C++的函数。
      C语言不支持函数重载，因此也就不难理解为什么一个C链接指示只能用于说明一组重载函数中的某一个了：[插图]
// 错误：两个extern "C"函数的名字相同
extern "C" void print(const char*);
extern "C" void print(int);
      如果在一组重载函数中有一个是C函数，则其余的必定都是C++函数：[插图]
class SmallInt {};
class BigNum {};
// C函数可以在C或C++程序中调用
// C++函数重载了该函数，可以在C++程序中调用
extern "C" double calc(double);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);
      C版本的calc函数可以在C或C++程序中调用，而使用了类类型形参的C++函数只能在C++程序中调用。上述性质与声明的顺序无关。
    */

    return 0;
}